package genetic_algorithm;

import java.util.List;
import java.util.Map.Entry;

import utils.GAretVal;

public class GeneticAlgorithm {
	
	private double crossover_rate = 0; // chance to perform crossover
	private double mutaion_rate = 0; // chance to perform mutation
	
	private FitnessFunction fitnessFun;
	private Crossover crossover;
	private Selection selector;
	Mutation mutator;
	private boolean remove;
		
	public GeneticAlgorithm(FitnessFunction fitnessFun, Crossover crossover,
			Selection selector, Mutation mutator, double crossover_rate,
			double mutaion_rate) {
		super();
		this.fitnessFun = fitnessFun;
		this.crossover = crossover;
		this.selector = selector;
		this.mutator = mutator;
		this.crossover_rate = crossover_rate;
		this.mutaion_rate = mutaion_rate;
		remove = selector.isRemove();
	}
	
	public void setCrossover_rate(double crossover_rate) {
		this.crossover_rate = crossover_rate;
	}
	
	public void setMutaion_rate(double mutaion_rate) {
		this.mutaion_rate = mutaion_rate;
	}
	
	public void setSelector(Selection selector) {
		this.selector = selector;
	}
	
	/**
	 * Given an initial population of chromosomes, executes the genetic algorithm:
	 * 1. Selects pairs of parent chromosomes
	 * 2. Mates pairs and gets two offsprings
	 * 3. Mutates the new chromosomes
	 * And repeats the above for the new population.
	 * This procedure is repeated for maxGen generations. At the end, the final
	 * population is returned.
	 * For each generation, average fitness and best fitness are calculated
	 * @param pop initial population
	 * @param maxGen maximal number of generations to run
	 * @param elite flag to indicate if should perform elitism or not
	 * @param eliteAmount number of chromosomes in the elite group
	 * @return final chromosome population resulting from the genetic algorithm and
	 * average and best fitness for each generation
	 */
	public GAretVal execute(Population pop, int maxGen, boolean elite, int eliteAmount)
	{

		// initialize return value to hold the execution data
		GAretVal execData = new GAretVal();
		
		int popSize = pop.getSize();
		int eliteSize = elite ? eliteAmount : 0; 
		// initialize initial populations
		Population curPop = pop, newPop = new Population(fitnessFun);
		
		
		int i = 0;
		// repeat the genetic algorithm scheme for specified number of generations
		for (i = 0; i < maxGen; i++) {

			// update average and best fitness values of current generation
			execData.addGenData(curPop.getBestFitness(), curPop.getFitnessAverage(), curPop.getWorstFitness());
			
			// TODO DEBUG PRINT!!!
//			System.out.println("#iter = " + i + ": best fitness= " + execData.getBestFitness(i)+
//					"\tFitness average= " + execData.getAvgFitness(i) +"\t\tForegin chromosomes: "+curPop.getAmountOfForeignChromosomes());
			System.out.println("#iter = " + i + ": best fitness= " + execData.getBestFitness(i)+
					"\tFitness average = " + execData.getAvgFitness(i) + "\tWorst fitness = " + execData.getWorstFitness(i));
			// 
			
			// set population from which pairs are selected
			selector.setPopulation(curPop);
			
			// if should, perform elitism
			if (elite) {
				List<Entry<Chromosome,Double>> elitsList = selector.getElite(eliteAmount);
				for (Entry<Chromosome,Double> entry : elitsList) {
					newPop.addChromosome(entry.getKey(), entry.getValue());
				}
			}

			// select chromosomes pairs and perform crossover between them
			Entry<Chromosome, Chromosome> parentsPair = selector.getNextPair();
			if(!remove) // TODO DEBUG!!! USED TO BE if(remove)
			{
				for(int j = 0 ; j < (popSize-eliteSize)/2 ; ++j)
				{
					// perform crossover
					generateNextPop(newPop, parentsPair);					
					// choose a new pair
					parentsPair = selector.getNextPair();
				}				
			}
			else
			{
				while (parentsPair != null) {					
					// perform crossover
					generateNextPop(newPop, parentsPair);					
					// choose a new pair
					parentsPair = selector.getNextPair();
				}				
			}
			
//			System.out.println("new pop size: "+newPop.getSize()); // TODO DEBUG PRINT
			
			// set next generation
			newPop.sort();
			curPop = newPop;
			newPop = new Population(fitnessFun);
		}
		
		System.out.println("#iter = " + i + ": best fitness= " + execData.getBestFitness(i-1)+
				"\tFitness average = " + execData.getAvgFitness(i-1) + "\tWorst fitness = " + execData.getWorstFitness(i-1));
		
		// add most fitting chromosome to return value
		execData.setSolution(curPop.getBestChromosome());
		
		// return data of the execution
		return execData;
	}


	private void generateNextPop(Population newPop,
			Entry<Chromosome, Chromosome> parentsPair) {
		Entry<Chromosome, Chromosome> childrenPair = crossover.mate(
				parentsPair, crossover_rate, mutator, mutaion_rate);
		
		// update population of the next generation
		newPop.addChromosome(childrenPair.getKey(), fitnessFun);
		newPop.addChromosome(childrenPair.getValue(), fitnessFun);
//				System.out.println("pop size: "+curPop.getSize()); // TODO DEBUG PRINT
	}
	
}
